package whsw.tool;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 表格工具
 *
 * @see TableInfo
 *
 * @author flutterdash@qq.com
 * @since 2021/6/20 14:59
 */
public class TextTable {

    /**
     * 根据一个标题列表和二维集合构建出一个表格
     * 用于在屏幕上显示二维表格
     *
     * @param head 标题行
     * @param body 内容
     * @return 表格字符串
     */
    public static String build(TableInfo head, Collection<TableInfo> body) {
        List<String> headList = head.getTitle();
        Collection<List<String>> bodyColl = body.stream()
                .map(TableInfo::toRecord)
                .peek(info -> TableInfo.checkForConsistency(headList, info))
                .collect(Collectors.toList());

        return new TextTable(headList, bodyColl).buildTable();
    }

    /**
     * 计算字符串在屏幕上占用的宽度<br>
     * 计算方式:  假如是普通字符, 例如ascii码, 则占用1个位置; 假如是特殊字符, 例如中文, 则占用两个位置
     *
     * 简化计算, 只处理中文和英文
     *
     * @param s 一个字符串
     * @return 返回字符串在屏幕上占用的宽度
     */
    public static int stringLen(String s) {
        if (s == null)
            return 0;

        int len = s.length();
        for (int i = 0; i<s.length(); i++) {
            int code = s.charAt(i);
            // 中文的在 unicode 编码中的区间
            if (code >= 19968 && code <= 40869)
                len++;
        }
        return len;
    }

    // 在 buff 末尾追加 [repeatCount] 个 [symbol]
    private static StringBuilder appendChars(StringBuilder buff, char symbol, int repeatCount) {
        if (repeatCount == 0)
            return buff;
        for (int i = 0; i<repeatCount; i++)
            buff.append(symbol);
        return buff;
    }

    // 在 buff 末尾添加一个换行符
    private static void appendWhitLineBreak(StringBuilder buff, String content) {
        buff.append(content).append(SEP);
    }

    /**
     * 构建表的逻辑:
     * +------+------+-------------+
     * | col1 | col2 | col3        | 标题行内容的上一行和下一行都是相同的分隔行
     * +------+------+-------------+
     * | as   | as   | hello world | 且不同行的相同列占用的宽度都是相同的
     * | as   | as   | 1           | 每一个单元格的文本内容默认居左
     * | as   | as   |             | 假如有一个单元格是空白的, 则这个位置仍然会用空格显示
     * +------+------+-------------+ 末尾也有一个分隔行
     */
    private String buildTable() {
        int[] lens = calcLenOfColumn();            // 计算每一列需要的宽度(单位:字符)
        String splitLine = buildSplitLine(lens);   // 一个行分割线
        StringBuilder buff = new StringBuilder();

        // 输出标题行
        appendWhitLineBreak(buff, splitLine);
        buildArray(buff, head, lens).append(SEP);
        appendWhitLineBreak(buff, splitLine);

        // 输出表格体
        for (List<String> line : body)
            buildArray(buff, line, lens).append(SEP);
        appendWhitLineBreak(buff, splitLine);

        return buff.toString();
    }

    // 返回一个int数组, 数组中每一个元素代表其下标所对应列的宽度(在屏幕上显示占用的宽度(单位字符))
    private int[] calcLenOfColumn() {
        int[] lens = new int[ head.size() ];
        int titleSize = head.size();

        for (int i = 0; i<head.size(); i++)
            lens[i] = checkAndMark(head.get(i));
        for (List<String> line : body) {
            for (int i = 0; i<head.size(); i++) {
                if (i < titleSize) {
                    lens[i] = Math.max(checkAndMark(line.get(i)), lens[i]);
                } else {
                    break;
                }
            }
        }
        return lens;
    }

    private int checkAndMark(String s) {
        int realLen = stringLen(s);
        if (realLen != s.length())
            onlyAsciiCode = false;
        return realLen;
    }

    private String buildSplitLine(int[] lens) {
        StringBuilder buff = new StringBuilder();
        buff.append('+');
        for (int len : lens) {
            appendChars(buff, '-', len + 2);
            buff.append('+');
        }
        return buff.toString();
    }

    // 构建一行内容
    private StringBuilder buildArray(StringBuilder buff, List<String> row, int[] lens) {
        buff.append('|');
        for (int column = 0; column<lens.length; column++) {
            int currentColumn = lens[column];
            if (column >= row.size()) {
                appendChars(buff, ' ', currentColumn + 2).append('|');
            } else {
                buff.append(' ');
                buildCell(buff, currentColumn, row.get(column));
                buff.append(' ').append('|');
            }
        }
        return buff;
    }

    /**
     * 构建一个单元格, 并把单元格的内容追加到 buff 后:
     * 原理 ->
     * 1. 每一列都已经计算出指定的宽度, 需要将文本限制到这个宽度内
     * 2. 如果文本的实际宽度小于指定宽度, 则缺少的部分用空格补位
     * 3. 如果文本的实际宽度大于指定宽度, 多出来的部分去掉, 并用省略号表示
     */
    private void buildCell(StringBuilder buff, int width, String text) {
        int realWidth = onlyAsciiCode ? text.length() : stringLen(text);
        int diff = width - realWidth;
        if (realWidth <= width) {
            buff.append(text);
            appendChars(buff, ' ', diff);
        } else {
            if (width <= 3) {
                appendChars(buff, '.', width);
            } else {
                for (int pos = 0; pos < (width - 3); pos++)
                    buff.append(text.charAt(pos));
                appendChars(buff, '.', 3);
            }
        }
    }

    static final char SEP = '\n';            // 换行符, 一个回车

    final List<String> head;                 // 表格的标题行
    final Collection<List<String>> body;     // 表格的内容

    boolean onlyAsciiCode = true;            // 表格的内容是否由纯 ascii 编码构成?

    // 私有构造器, 这个类不允许在外部被构建
    private TextTable(List<String> head, Collection<List<String>> body) {
        this.head = head;
        this.body = body;
    }

}
